﻿/*************************************************
taurus协议包字节流解析
author：Daixiwei
**************************************************/
using System;
using System.IO;

namespace taurus.client
{
    public enum PacketReadState
    {
        WAIT_NEW_PACKET,
        WAIT_DATA_SIZE,
        WAIT_DATA_SIZE_FRAGMENT,
        WAIT_DATA,
        INVALID_DATA
    }

    public class IOHandler
    {
        private const string ACTION_ID = "a";
        private const string PARAM_ID = "p";
        static readonly int INT_BYTE_SIZE = 4;

        private readonly ByteArray EMPTY_BUFFER = new ByteArray();
        private int _skipBytes = 0;
        private ByteArray _buffer;
        private int _expectedLength = -1;

        private SocketEngine _engine;
        SystemController _controller;
        /// <summary>
        /// 读取状态
        /// </summary>
        private PacketReadState _readState;
        private bool _compressed = false;

        public IOHandler(SocketEngine engine, SystemController controller)
        {
            _buffer = new ByteArray();
            _engine = engine;
            _controller = controller;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private ByteArray handleDataSize(ByteArray data)
        {
            int num = data.readInt();
            int pos = INT_BYTE_SIZE;
            if (num != -1)
            {
                _expectedLength = num;
                data = resizeByteArray(data, pos, data.length - pos);
                _readState = PacketReadState.WAIT_DATA;
                return data;
            }
            _readState = PacketReadState.WAIT_DATA_SIZE_FRAGMENT;
            _buffer.writeBytes(data.bytes);
            data = EMPTY_BUFFER;
            return data;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private ByteArray handleDataSizeFragment(ByteArray data)
        {
            int count = (INT_BYTE_SIZE - _buffer.length);
            if (data.length >= count)
            {
                _buffer.writeBytes(data.bytes, 0, count);
                int num2 = INT_BYTE_SIZE;
                ByteArray array = new ByteArray();
                array.writeBytes(_buffer.bytes, 0, num2);
                array.position = 0;
                int num3 = array.readInt();
                _expectedLength = num3;
                _buffer = new ByteArray();
                _readState = PacketReadState.WAIT_DATA;
                if (data.length > count)
                {
                    data = resizeByteArray(data, count, data.length - count);
                    return data;
                }
                data = EMPTY_BUFFER;
                return data;
            }
            _buffer.writeBytes(data.bytes);
            data = EMPTY_BUFFER;
            return data;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private ByteArray handleInvalidData(ByteArray data)
        {
            if (_skipBytes == 0)
            {
                _readState = PacketReadState.WAIT_NEW_PACKET;
                return data;
            }
            var pos = Math.Min(data.length, _skipBytes);
            data = resizeByteArray(data, pos, data.length - pos);
            _skipBytes -= pos;
            return data;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        private ByteArray handleNewPacket(ByteArray data)
        {
            _buffer = new ByteArray();
            _expectedLength = -1;
		    _compressed = data.readByte() > 0;
		    data = resizeByteArray(data, 1, data.length - 1);
		    _readState = PacketReadState.WAIT_DATA_SIZE;
		    return data;
	    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="data"></param>
    /// <returns></returns>
    private ByteArray handlePacketData(ByteArray data)
        {
            int count = _expectedLength - _buffer.length;
            bool flag = data.length > count;
            ByteArray array = new ByteArray(data.bytes);
            try
            {
                if (data.length >= count)
                {
                    _buffer.writeBytes(data.bytes, 0, count);
                    if (_compressed)
                    {
                        _buffer.uncompress();
                    }
                    dispatchRequest(_buffer);
                    _readState = PacketReadState.WAIT_NEW_PACKET;
                }
                else
                {
                    _buffer.writeBytes(data.bytes);
                }
                if (flag)
                {
                    data = resizeByteArray(data, count, data.length - count);
                    return data;
                }
                data = EMPTY_BUFFER;
            }
            catch (Exception exception)
            {
                Logger.error("Error handling data ", exception);
                _skipBytes = count;
                _readState = PacketReadState.INVALID_DATA;
                return array;
            }
            return data;
        }

        private void dispatchRequest(ByteArray _buffer)
        {
            ITObject requestObject = TObject.newFromBinaryData(_buffer);
            Packet packet = new Packet();
            if (requestObject.isNull(ACTION_ID))
            {
                throw new Exception("Request rejected: No Action ID in request!");
            }
            packet.actionID = requestObject.getByte(ACTION_ID);
            packet.parameters = requestObject.getTObject(PARAM_ID);
            _controller.handlePacket(packet);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="data"></param>
        public void onDataRead(ByteArray data)
        {
            data.position = 0;
            while (data.length > 0)
            {
                if (_readState == PacketReadState.WAIT_NEW_PACKET)
                {
                    data = handleNewPacket(data);
                }
                else
                {
                    if (_readState == PacketReadState.WAIT_DATA_SIZE)
                    {
                        data = handleDataSize(data);
                        continue;
                    }
                    if (_readState == PacketReadState.WAIT_DATA_SIZE_FRAGMENT)
                    {
                        data = handleDataSizeFragment(data);
                        continue;
                    }
                    if (_readState == PacketReadState.WAIT_DATA)
                    {
                        data = handlePacketData(data);
                        continue;
                    }
                    if (_readState == PacketReadState.INVALID_DATA)
                    {
                        data = handleInvalidData(data);
                    }
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="message"></param>
        public void onDataWrite(Packet packet)
        {
            if (packet.useUdp && _engine._udpSocket == null)
            {
                packet.useUdp = false;
            }
            ByteArray binData = new ByteArray();

            ITObject obj2 = new TObject();
            obj2.putByte(ACTION_ID, (byte)packet.actionID);
            if (packet.parameters != null)
            {
                obj2.putTObject(PARAM_ID, packet.parameters);
            }
            var array2 = obj2.toBinary();
            bool compression = array2.length > _engine.protocolCompression;
            if (compression)
            {
                array2.compress();
            }
            binData.writeByte((byte)(compression ? 1 : 0));
            binData.writeInt(array2.length);
            if (packet.useUdp)
            {
                binData.writeInt(_engine.sessionId);
            }
            binData.writeBytes(array2.bytes);

            if (_engine._socket.isConnected())
            {
                
                if (packet.useUdp)
                {
                    if (_engine._udpSocket.isConnected()&&!_engine._udpSocket.send(binData.bytes))
                    {
                        _engine.enqueueStatusCallback(SocketCode.SendError);
                    }
                }
                else
                {
                    if (!_engine._socket.send(binData.bytes))
                    {
                        _engine.enqueueStatusCallback(SocketCode.SendError);
                    }
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="array"></param>
        /// <param name="pos"></param>
        /// <param name="len"></param>
        /// <returns></returns>
        private ByteArray resizeByteArray(ByteArray array, int pos, int len)
        {
            byte[] dst = new byte[len];
            Buffer.BlockCopy(array.bytes, pos, dst, 0, len);
            return new ByteArray(dst);
        }


        
    }
}
